from BFGlobalService_services import *
from BFGlobalService_services_types import ns0 as BFG
from BFExchangeService_services import *
from BFExchangeService_services_types import ns0 as BFE

##################################################
# Exchange wrapper class.

class Exchange:

    EXCHANGE_UK = 1
    EXCHANGE_AU = 2
    
    MINIMUM_PRICE = 1.01
    MAXIMUM_PRICE = 1000
    MINIMUM_SIZE = 2
    MINIMUM_SP_LIABILITY_B = 2
    MINIMUM_SP_LIABILITY_L = 10

    def __init__(self):
        self._loc_g = BFGlobalServiceLocator()
        self._port_g = self._loc_g.getBFGlobalService(url="https://api.betfair.com/global/v3/BFGlobalService")
        self._header_g = BFG.APIRequest_Def("header")
        self._header_g._clientStamp = 0
        self._header_g._sessionToken = None
        self._currency = None
        
        self._loc_e = BFExchangeServiceLocator()
        self._port_e_uk = self._loc_e.getBFExchangeService(url="https://api.betfair.com/exchange/v5/BFExchangeService")
        self._port_e_au = self._loc_e.getBFExchangeService(url="https://api-au.betfair.com/exchange/v5/BFExchangeService")
        self._header_e = BFE.APIRequest_Def("header")
        self._header_e._clientStamp = 0
        self._header_e._sessionToken = None
        self._ports_e = { Exchange.EXCHANGE_UK: self._port_e_uk, Exchange.EXCHANGE_AU: self._port_e_au }

    ##############################################
    # Account methods.

    def login(self, username, password):
        req = loginIn()
        req._request = req.new_request()
        req._request._ipAddress = "192.168.0.1"
        req._request._locationId = 0
        req._request._password = password
        req._request._productId = 82
        req._request._username = username
        req._request._vendorSoftwareId = 0
        resp = self._port_g.login(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        self._currency = resp._Result._currency

    def logout(self):
        req = logoutIn()
        req._request = req.new_request()
        req._request._header = self._header_g
        resp = self._port_g.logout(req)
        self._reset_session_token(None)
        self._handle_error(resp)
        self._currency = None

    def keep_alive(self):
        req = keepAliveIn()
        req._request = req.new_request()
        req._request._header = self._header_g
        resp = self._port_g.keepAlive(req)
        self._reset_session_token(resp)

    def get_account_funds(self, exchange_id):
        port = self._ports_e[exchange_id]
        req = getAccountFundsIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        resp = port.getAccountFunds(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result

    def transfer_funds(self, from_exchange_id, to_exchange_id, amount):
        req = transferFundsIn()
        req._request = req.new_request()
        req._request._header = self._header_g
        req._request._sourceWalletId = from_exchange_id
        req._request._targetWalletId = to_exchange_id
        req._request._amount = amount
        resp = self._port_g.transferFunds(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
    
    ##############################################
    # Event and market methods.

    def get_all_event_types(self):
        req = getAllEventTypesIn()
        req._request = req.new_request()
        req._request._header = self._header_g
        resp = self._port_g.getAllEventTypes(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._eventTypeItems._EventType

    def get_active_event_types(self):
        req = getActiveEventTypesIn()
        req._request = req.new_request()
        req._request._header = self._header_g
        resp = self._port_g.getActiveEventTypes(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._eventTypeItems._EventType

    def get_events(self, event_id):
        req = getEventsIn()
        req._request = req.new_request()
        req._request._header = self._header_g
        req._request._eventParentId = event_id
        resp = self._port_g.getEvents(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._eventItems._BFEvent, resp._Result._marketItems._MarketSummary

    def get_market(self, exchange_id, market_id):
        port = self._ports_e[exchange_id]
        req = getMarketIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._includeCouponLinks = False
        req._request._marketId = market_id
        resp = port.getMarket(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._market

    def get_market_prices(self, exchange_id, market_id, compressed=True):
        port = self._ports_e[exchange_id]
        if compressed:
            req = getMarketPricesCompressedIn()
            req._request = req.new_request()
            req._request._header = self._header_e
            req._request._marketId = market_id
            resp = port.getMarketPricesCompressed(req)
            self._reset_session_token(resp)
            self._handle_error(resp)
            return self._decompress_market_prices(resp._Result._marketPrices)
        else:
            req = getMarketPricesIn()
            req._request = req.new_request()
            req._request._header = self._header_e
            req._request._marketId = market_id
            resp = port.getMarketPrices(req)
            self._reset_session_token(resp)
            self._handle_error(resp)
            return resp._Result._marketPrices

    def get_market_traded_volume(self, exchange_id, market_id, selection_id):
        port = self._ports_e[exchange_id]
        req = getMarketTradedVolumeIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._asianLineId = 0
        req._request._currencyCode = self._currency
        req._request._marketId = market_id
        req._request._selectionId = selection_id
        resp = port.getMarketTradedVolume(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._priceItems._VolumeInfo

    def get_detail_available_mkt_depth(self, exchange_id, market_id, selection_id):
        port = self._ports_e[exchange_id]
        req = getDetailAvailableMktDepthIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._asianLineId = 0
        req._request._currencyCode = self._currency
        req._request._marketId = market_id
        req._request._selectionId = selection_id
        resp = port.getDetailAvailableMktDepth(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._priceItems._AvailabilityInfo
    
    def find_runner(market, selection_id):
        for runner in market._runners._Runner:
            if runner._selectionId == selection_id:
                return runner
        return None
    find_runner = staticmethod(find_runner)
    
    def find_runner_prices(market_prices, selection_id):
        for runner_prices in market_prices._runnerPrices._RunnerPrices:
            if runner_prices._selectionId == selection_id:
                return runner_prices
        return None
    find_runner_prices = staticmethod(find_runner_prices)
    
    ##############################################
    # Betting methods.

    def get_MU_bet(self, exchange_id, betId):
        req = getMUBetsIn()
        port = self._ports_e[exchange_id]
        req._request = req.new_request()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._betIds = req._request.new_betIds()
        req._request._betIds.BetId.append(betId)
        req._request._betStatus = "MU" 
        req._request._orderBy = "PLACED_DATE"
        req._request._recordCount = 1
        req._request._marketId = 0 
        req._request._sortOrder = "DESC"
        req._request._startRecord = 0
        req._request._excludeLastSecond = True
        resp = port.getMUBets(req)
        self._reset_session_token(resp)
        return resp._Result.Bets

    def get_MU_bets(self, exchange_id, betIds):
        req = getMUBetsIn()
        port = self._ports_e[exchange_id]
        req._request = req.new_request()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._betIds = req._request.new_betIds()
        rcount = 0
        for betId in betIds:
            req._request._betIds.BetId.append(betId)
        req._request._betStatus = "MU" 
        req._request._orderBy = "PLACED_DATE"
        req._request._recordCount = len(betIds)
        req._request._marketId = 0 
        req._request._sortOrder = "DESC"
        req._request._startRecord = 0
        req._request._excludeLastSecond = True
        resp = port.getMUBets(req)
        self._reset_session_token(resp)
        return resp._Result.Bets
    
    def get_current_bets(self, exchange_id, bet_status, pager, market_id=0, detailed=False, order_by="NONE"):
        if pager.is_finished():
            return []
        record = pager._record
        records = 100    
        port = self._ports_e[exchange_id]
        req = getCurrentBetsIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._betStatus = bet_status
        req._request._detailed = detailed
        req._request._marketId = market_id
        req._request._orderBy = order_by
        req._request._recordCount = records
        req._request._startRecord = record
        req._request._noTotalRecordCount = True
        resp = port.getCurrentBets(req)
        self._reset_session_token(resp)
        if resp._Result._errorCode == "OK":
            if len(resp._Result._bets._Bet) < records:
                pager.finish()
            else:
                pager._record =  record + records
        elif resp._Result._errorCode == "NO_RESULTS":
            resp._Result._bets = resp._Result.new_bets()
            resp._Result._bets._Bet = []
            pager.finish()
        else:
            pager.finish()
            self._handle_error(resp)
        return resp._Result._bets._Bet

    def get_bet_history(self, exchange_id, event_type_ids, bet_types_included, placed_date_from, placed_date_to, pager, market_id=0, market_types_included=["O"], detailed=False, sort_by="NONE"):
        if pager.is_finished():
            return []
        record = pager._record
        records = 100
        port = self._ports_e[exchange_id]
        req = getBetHistoryIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._betTypesIncluded = bet_types_included
        req._request._detailed = detailed
        req._request._eventTypeIds = BFE.ArrayOfInt_Def("eventTypeIds")
        req._request._eventTypeIds._int = event_type_ids
        req._request._marketId = market_id
        req._request._marketTypesIncluded = BFE.ArrayOfMarketTypeEnum_Def("marketTypesIncluded")
        req._request._marketTypesIncluded._MarketTypeEnum = market_types_included
        req._request._placedDateFrom = placed_date_from
        req._request._placedDateTo = placed_date_to
        req._request._recordCount = records
        req._request._startRecord = record
        req._request._sortBetsBy = sort_by
        resp = port.getBetHistory(req)
        self._reset_session_token(resp)
        if resp._Result._errorCode == "OK":
            if len(resp._Result._betHistoryItems._Bet) < records:
                pager.finish()
            else:
                pager._record =  record + records
        elif resp._Result._errorCode == "NO_RESULTS":
            resp._Result._betHistoryItems = resp._Result.new_betHistoryItems()
            resp._Result._betHistoryItems._Bet = []
            pager.finish()
        else:
            pager.finish()
            self._handle_error(resp)
        return resp._Result._betHistoryItems._Bet

    def get_market_profit_and_loss(self, exchange_id, market_id, include_settled_bets=False, include_bsp_bets=True, net_of_commission=False):
        port = self._ports_e[exchange_id]
        req = getMarketProfitAndLossIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._includeBspBets = include_bsp_bets
        req._request._includeSettledBets = include_settled_bets
        req._request._marketId = market_id
        req._request._netOfCommission = net_of_commission
        resp = port.getMarketProfitAndLoss(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result
        
    def new_place_bet(market_id, selection_id, bet_type, price, size, bet_persistence_type="NONE", bet_category_type="E", bsp_liability=0, asian_line_id=0):
        place_bet = BFE.PlaceBets_Def("PlaceBets")
        place_bet._asianLineId = asian_line_id
        place_bet._betCategoryType = bet_category_type
        place_bet._betPersistenceType = bet_persistence_type
        place_bet._betType = bet_type
        place_bet._bspLiability = bsp_liability
        place_bet._marketId = market_id
        place_bet._price = price
        place_bet._selectionId = selection_id
        place_bet._size = size
        return place_bet
    new_place_bet = staticmethod(new_place_bet)

    def place_bet(self, exchange_id, bet):
        return self.place_bets(exchange_id, [bet])[0]

    def place_bets(self, exchange_id, bets):
        port = self._ports_e[exchange_id]
        req = placeBetsIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._bets = req._request.new_bets()
        req._request._bets._PlaceBets = bets
        resp = port.placeBets(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._betResults._PlaceBetsResult
        
    def new_update_bet(bet_id, old_price, new_price, old_size, new_size, old_bet_persistence_type="NONE", new_bet_persistence_type="NONE"):
        update_bet = BFE.UpdateBets_Def("UpdateBets")
        update_bet._betId = bet_id
        update_bet._oldPrice = old_price
        update_bet._newPrice = new_price
        update_bet._oldSize = old_size
        update_bet._newSize = new_size
        update_bet._oldBetPersistenceType = old_bet_persistence_type
        update_bet._newBetPersistenceType = new_bet_persistence_type
        return update_bet
    new_update_bet = staticmethod(new_update_bet)

    def update_bet(self, exchange_id, bet):
        return self.update_bets(exchange_id, [bet])[0]

    def update_bets(self, exchange_id, bets):
        port = self._ports_e[exchange_id]
        req = updateBetsIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._bets = req._request.new_bets()
        req._request._bets._UpdateBets = bets
        resp = port.updateBets(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._betResults._UpdateBetsResult
        
    def new_cancel_bet(bet_id):
        cancel_bet = BFE.CancelBets_Def("CancelBets")
        cancel_bet._betId = bet_id
        return cancel_bet
    new_cancel_bet = staticmethod(new_cancel_bet)

    def cancel_bet(self, exchange_id, bet):
        return self.cancel_bets(exchange_id, [bet])[0]

    def cancel_bets(self, exchange_id, bets):
        port = self._ports_e[exchange_id]
        req = cancelBetsIn()
        req._request = req.new_request()
        req._request._header = self._header_e
        req._request._bets = req._request.new_bets()
        req._request._bets._CancelBets = bets
        resp = port.cancelBets(req)
        self._reset_session_token(resp)
        self._handle_error(resp)
        return resp._Result._betResults._CancelBetsResult
    
    ##############################################
    # Price methods.
    
    def is_valid_price(price):
        return (price >= MINIMUM_PRICE) and (price <= MAXIMUM_PRICE)
    is_valid_price = staticmethod(is_valid_price)
    
    def increment_price(price):
        if price < 1.01:
            return price
        elif price < 2.00:
            return price + 0.01
        elif price < 3.00:
            return price + 0.02
        elif price < 4.00:
            return price + 0.05
        elif price < 6.00:
            return price + 0.10
        elif price < 10.00:
            return price + 0.20
        elif price < 20.00:
            return price + 0.50
        elif price < 30.00:
            return price + 1.00
        elif price < 50.00:
            return price + 2.00
        elif price < 100.00:
            return price + 5.00
        elif price < 1000.00:
            return price + 10.00
        else:
            return price
    increment_price = staticmethod(increment_price)
    
    def decrement_price(price):
        if price <= 1.01:
            price = price
        elif price <= 2.00:
            price = price - 0.01
        elif price <= 3.00:
            price = price - 0.02
        elif price <= 4.00:
            price = price - 0.05
        elif price <= 6.00:
            price = price - 0.10
        elif price <= 10.00:
            price = price - 0.20
        elif price <= 20.00:
            price = price - 0.50
        elif price <= 30.00:
            price = price - 1.00
        elif price <= 50.00:
            price = price - 2.00
        elif price <= 100.00:
            price = price - 5.00
        elif price <= 1000.00:
            price = price - 10.00
        else:
            return price
    decrement_price = staticmethod(decrement_price)

    ##############################################
    # Errors
    
    class Error(Exception):
        def __init__(self, message):
            self.message = message

        def __str__(self):
            return repr(self.message)

    class ThrottleError(Error):
        def __init__(self):
            self.message = "Throttled"

        def __str__(self):
            return repr(self.message)
    
    ##############################################
    # Pager for paged calls

    class Pager:    
      def __init__(self):
        self._record = 0

      def finish(self):
        self._record = -1

      def is_finished(self):
        return self._record == -1
    
    ##############################################
    # Implementation methods.
    
    def _reset_session_token(self, resp):
        if resp is None:
            self._header_g._sessionToken = None
            self._header_e._sessionToken = None
        else:
            self._header_g._sessionToken = resp._Result._header._sessionToken
            self._header_e._sessionToken = resp._Result._header._sessionToken

    def _handle_error(self, resp):
        if resp._Result._header._errorCode == "THROTTLE_EXCEEDED":
            raise Exchange.ThrottleError
        elif resp._Result._header._errorCode != "OK":
            raise Exchange.Error(resp._Result._header._errorCode)
        if resp._Result._errorCode != "OK":
            raise Exchange.Error(resp._Result._errorCode)           
   
    def _decompress_market_prices(compressed):
        compressed = Exchange._decompress_escape_pre_parse(compressed)
        market_prices = BFE.MarketPrices_Def("marketPrices")
        runner_prices_list = []
        market = True
        compressed_runners = compressed.split(":")
        for compressed_runner in compressed_runners:
            if market:
                market = False
                values = compressed_runner.split("~")
                if len(values) >= 9:
                    market_prices._marketId = int(values[0])
                    market_prices._currencyCode = Exchange._decompress_escape_post_parse(values[1])
                    market_prices._marketStatus = values[2]
                    market_prices._delay = int(values[3])
                    market_prices._numberOfWinners = int(values[4])
                    market_prices._marketInfo = Exchange._decompress_escape_post_parse(values[5])
                    market_prices._discountAllowed = Exchange._decompress_boolean(values[6])
                    market_prices._marketBaseRate = float(values[7])
                    market_prices._lastRefresh = int(Exchange._decompress_default(values[8], "0"))
                    if len(values) >= 10:
                        market_prices._removedRunners = Exchange._decompress_escape_post_parse(values[9])
                    if len(values) >= 11:
                        market_prices._bspMarket = (values[10] == "Y") or (values[10] == "y")
                else:
                    raise Exchange.Error("Too few values for market in compressed prices: %s" % (compressed_runner))
            else:
                runner_prices = BFE.RunnerPrices_Def("runnerPrices")
                prices_back_list = []
                prices_lay_list = []
                selection = True
                compressed_prices = compressed_runner.split("|")
                for compressed_price in compressed_prices:
                    if selection:
                        selection = False
                        values = compressed_price.split("~")
                        if len(values) >= 7:
                            runner_prices._selectionId = int(values[0])
                            runner_prices._sortOrder = int(values[1])
                            runner_prices._totalAmountMatched = float(Exchange._decompress_default(values[2], "0"))
                            runner_prices._lastPriceMatched = float(Exchange._decompress_default(values[3], "0"))
                            runner_prices._handicap = float(Exchange._decompress_default(values[4], "0"))
                            runner_prices._reductionFactor = float(Exchange._decompress_default(values[5], "0"))
                            runner_prices._vacant = Exchange._decompress_boolean(values[6])
                            if len(values) >= 10:
                                runner_prices._farBSP = Exchange._decompress_sp(values[7])
                                runner_prices._nearBSP = Exchange._decompress_sp(values[8])
                                runner_prices._actualBSP = Exchange._decompress_sp(values[9])
                        else:
                            raise Exchange.Error("Too few values for runner in compressed prices: %s" % (compressed_price))
                    else:
                        price = None
                        index = 0
                        values = compressed_price.split("~")
                        for value in values:
                            if len(value) == 0:
                                break
                            mod_index = index % 4
                            if mod_index == 0:
                                price = BFE.Price_Def("price")
                                price._price = float(value)
                            elif mod_index == 1:
                                price._amountAvailable = float(value)
                            elif mod_index == 2:
                                price._betType = value
                            else:
                                price._depth = int(value)
                                if price._betType == "B":
                                    prices_lay_list.append(price)
                                else:
                                    prices_back_list.append(price)
                                price = None
                            index = index + 1
                runner_prices._bestPricesToBack = BFE.ArrayOfPrice_Def("bestPricesToBack")
                runner_prices._bestPricesToBack._Price = prices_back_list
                runner_prices._bestPricesToLay = BFE.ArrayOfPrice_Def("bestPricesToLay")
                runner_prices._bestPricesToLay._Price = prices_lay_list
                runner_prices_list.append(runner_prices)

        market_prices._runnerPrices = BFE.ArrayOfRunnerPrices_Def("runnerPrices")
        market_prices._runnerPrices._RunnerPrices = runner_prices_list
        return market_prices
    _decompress_market_prices = staticmethod(_decompress_market_prices)
    
    def _decompress_escape_pre_parse(compressed):
        if compressed.find("\\") != -1:
            compressed = compressed.replace("\\:", "\x01")
            compressed = compressed.replace("\\|", "\x02")
            compressed = compressed.replace("\\~", "\x03")
            compressed = compressed.replace("\\\\", "\x04")
        return compressed
    _decompress_escape_pre_parse = staticmethod(_decompress_escape_pre_parse)
    
    def _decompress_escape_post_parse(parsed):
        parsed = parsed.replace("\x01", ":")
        parsed = parsed.replace("\x02", "|")
        parsed = parsed.replace("\x03", "~")
        parsed = parsed.replace("\x04", "\\")
        return parsed
    _decompress_escape_post_parse = staticmethod(_decompress_escape_post_parse)

    def _decompress_default(value, default):
        if len(value) == 0:
            return default
        else:
            return value
    _decompress_default = staticmethod(_decompress_default)

    def _decompress_boolean(compressed):
        return (compressed == "true")
    _decompress_boolean = staticmethod(_decompress_boolean)

    def _decompress_sp(compressed):
        value = 0.0
        try:
            value = float(compressed)
        except:
            value = 0.0
        return value
    _decompress_sp = staticmethod(_decompress_sp)
    
    def _info(object, spacing=10, collapse=1):
        method_list = [method for method in dir(object) if callable(getattr(object, method))]
        process_func = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
        print "\n".join(["%s %s" %
            (method.ljust(spacing), process_func(str(getattr(object, method).__doc__)))
            for method in method_list])
    _info= staticmethod(_info)
